热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C++进阶:类的内存布局与虚函数类的实现细节

篇首语:本文由编程笔记#小编为大家整理,主要介绍了[C++ 提高] --- 类的存储 和 包含虚函数的类相关的知识,希望对你有一定的参考价值。 1 从内存四区的角度分析类的存储 如果一个类包括了数据和

篇首语:本文由编程笔记#小编为大家整理,主要介绍了[C++ 提高] --- 类的存储 和 包含虚函数的类相关的知识,希望对你有一定的参考价值。



1 从内存四区的角度分析类的存储

如果一个类包括了数据和函数,用这个类去实例化对象时,系统会为每一个对象分配存储空间。每个对象所占用的存储空间只是该对象的数据部分(虚函数指针和虚基类指针也属于数据部分)所占用的存储空间,而不包括函数代码所占用的存储空间。
C++编译系统会用一段空间来存放这个共同的函数代码段,在调用各对象的函数时,都去调用这个公用的函数代码。

  C++程序的内存格局通常分为四个区:全局数据区(data area),代码区(code area),栈区(stack area),堆区(heap area)(即自由存储区)。
  全局数据区存放全局变量,静态数据和常量;
  所有类成员函数和非成员函数代码存放在代码区;
  为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放在栈区;
  余下的空间都被称为堆区。

  根据这个解释,我们可以得知在类的定义时,类成员函数是被放在代码区,而类的静态成员变量在类定义时就已经在全局数据区分配了内存,因而它是属于类的。对于非静态成员变量,我们是在类的实例化过程中(构造对象)才在栈区或者堆区为其分配内存,是为每个对象生成一个拷贝,所以它是属于对象的。
  应当说明,常说的“某某对象的成员函数”,是从逻辑的角度而言的,而成员函数的存储方式,是从物理的角度而言的,二者是不矛盾的。


2 C++类分析

2.1 C++类的构成

类由两部分构成:


  • 数据成员:简单类型[char/short/long/int/double/float等]、复合类型[结构体/枚举/类类型等]
  • 函数成员:虚函数、非虚函数

2.2 数据成员的存储方式 - 内存对其原则

复合类型由简单类型组成,简单类型对其原则同C语言结构体内存对其原则,很早之前已经写过,可以参看here,这里进行简单回顾。
简单类型在类的对象中对齐方式,以字节为单位进行存储。

char 1
short 2
long 4
int 4
float 4
fouble 8

取类中最长的数据成员作为对齐原则。例如,类中最长为 double,那么就是8 个字节。


2.3 函数成员的存储方式

非虚函数是存放在代码区的,不占用类的存储空间。
在一个类的某个函数前加上virtual关键字,这个函数就变成了虚函数。编译器处理虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),这种数组成为虚函数表(virtual function table, vtbl),即,每个类使用一个虚函数表,每个类对象用一个虚表指针。


2.4 实例验证类大小

读完上面的分析,现在来看示例代码,进行验证

代码示例

#include <iostream>
/*******************Test1***********************/
class Test1
public:
Test1();
~Test1();
public:
int n1;
char c1;
short s1;
private:
int n2;
char c2;
short s2;
;
Test1::Test1()


Test1::~Test1()



/*******************Test1 end***********************/
/*******************Test2***********************/
class Test2
public:
Test2();
~Test2();
void func0();
friend void func1();
void func2() const ;
inline void func3() ;
static void func4() ;
// virtual void func5();
public:
int n1;
char c1;
short s1;
private:
int n2;
char c2;
short s2;
;
Test2::Test2()


Test2::~Test2()



void Test2::func0()


void func1()


void Test2::func2() const


inline void Test2::func3()


void Test2::func4()


// void Test2::func5()
//
//
/*******************Test2 end***********************/
int main(void)

Test1 test1_;
Test2 test2_;
printf("sizeof test1 &#61; %ld\\n", sizeof(test1_));
printf("sizeof test2 &#61; %ld\\n", sizeof(test2_));
return 0;

编译输出&#xff1a;

打开注释之后&#xff0c;编译输出【测试机器为x64】&#xff1a;

总结&#xff1a;


  • C&#43;&#43;编译系统中&#xff0c;数据和函数是分开存放的(函数放在代码区&#xff1b;数据主要放在栈区和堆区&#xff0c;静态/全局区以及文字常量区也有)&#xff0c;实例化不同对象时&#xff0c;只给数据分配空间&#xff0c;各个对象调用函数时都都跳转到(内联函数例外)找到函数在代码区的入口执行&#xff0c;可以节省拷贝多份代码的空间
  • 类的静态成员变量编译时被分配到静态/全局区&#xff0c;因此静态成员变量是属于类的&#xff0c;所有对象共用一份&#xff0c;不计入类的内存空间
  • 静态成员函数和非静态成员函数都是存放在代码区的&#xff0c;是属于类的&#xff0c;类可以直接调用静态成员函数&#xff0c;不可以直接调用非静态成员函数&#xff0c;两者主要的区别是有无this指针
  • 存在虚函数的类&#xff0c;会对应一个虚函数表&#xff0c;实例化对象时&#xff0c;每个对象会占用一个虚表指针

3 包含虚函数的类

3.1 概述

简单地说&#xff0c;每一个含有虚函数&#xff08;无论是其本身的&#xff0c;还是继承而来的&#xff09;的类都至少有一个与之对应的虚函数表&#xff0c;其中存放着该类所有的虚函数对应的函数指针。例&#xff1a;

其中&#xff1a;
B的虚函数表中存放着B::foo和B::bar两个函数指针。
D的虚函数表中存放的既有继承自B的虚函数B::foo&#xff0c;又有重写&#xff08;override&#xff09;了基类虚函数B::bar的D::bar&#xff0c;还有新增的虚函数D::quz。


3.2 虚函数表构造的过程

从编译器的角度来说&#xff0c;B的虚函数表很好构造&#xff0c;D的虚函数表构造过程相对复杂。下面给出了构造D的虚函数表的一种方式&#xff08;仅供参考&#xff09;&#xff1a;


3.3 虚函数调用过程

以下面的程序为例&#xff1a;

编译器只知道pb是B*类型的指针&#xff0c;并不知道它指向的具体对象类型 &#xff1a;pb可能指向的是B的对象&#xff0c;也可能指向的是D的对象。

但对于“pb->bar()”&#xff0c;编译时能够确定的是&#xff1a;此处operator->的另一个参数是B::bar&#xff08;因为pb是B*类型的&#xff0c;编译器认为bar是B::bar&#xff09;&#xff0c;而B::bar和D::bar在各自虚函数表中的偏移位置是相等的。

无论pb指向哪种类型的对象&#xff0c;只要能够确定被调函数在虚函数中的偏移值&#xff0c;待运行时&#xff0c;能够确定具体类型&#xff0c;并能找到相应vptr了&#xff0c;就能找出真正应该调用的函数。


推荐阅读
  • 题目描述:小K不幸被LL邪教洗脑,洗脑程度之深使他决定彻底脱离这个邪教。在最终离开前,他计划再进行一次亚瑟王游戏。作为最后一战,他希望这次游戏能够尽善尽美。众所周知,亚瑟王游戏的结果很大程度上取决于运气,但通过合理的策略和算法优化,可以提高获胜的概率。本文将详细解析洛谷P3239 [HNOI2015] 亚瑟王问题,并提供具体的算法实现方法,帮助读者更好地理解和应用相关技术。 ... [详细]
  • 在稀疏直接法视觉里程计中,通过优化特征点并采用基于光度误差最小化的灰度图像线性插值技术,提高了定位精度。该方法通过对空间点的非齐次和齐次表示进行处理,利用RGB-D传感器获取的3D坐标信息,在两帧图像之间实现精确匹配,有效减少了光度误差,提升了系统的鲁棒性和稳定性。 ... [详细]
  • 本文详细探讨了C语言中`extern`关键字的简易编译方法,并深入解析了预编译、`static`和`extern`的综合应用。通过具体的代码示例,介绍了如何在不同的文件之间共享变量和函数声明,以及这些关键字在编译过程中的作用和影响。文章还讨论了预编译过程中宏定义的使用,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • JVM参数设置与命令行工具详解
    JVM参数配置与命令行工具的深入解析旨在优化系统性能,通过合理设置JVM参数,确保在高吞吐量的前提下,有效减少垃圾回收(GC)的频率,进而降低系统停顿时间,提升服务的稳定性和响应速度。此外,本文还将详细介绍常用的JVM命令行工具,帮助开发者更好地监控和调优JVM运行状态。 ... [详细]
  • POJ 1696: 空间蚂蚁算法优化与分析
    针对 POJ 1696 的空间蚂蚁算法进行了深入的优化与分析。本研究通过改进算法的时间复杂度和空间复杂度,显著提升了算法的效率。实验结果表明,优化后的算法在处理大规模数据时表现优异,能够有效减少计算时间和内存消耗。此外,我们还对算法的收敛性和稳定性进行了详细探讨,为实际应用提供了可靠的理论支持。 ... [详细]
  • 1. 给定一个包含 n 个整数的数组 a 和一个整数 x,需要判断数组中是否存在两个不同的元素,它们的和恰好等于 x。2. 反转数对问题:对于一个包含 n 个不同元素的数组 A[1...n],如果存在 i < j 且 A[i] > A[j],则称 (i, j) 为一个反转数对。本文将详细探讨这两种与归并排序相关的算法题目,并提供高效的解决方案。 ... [详细]
  • 本文详细介绍了如何在Linux系统中搭建51单片机的开发与编程环境,重点讲解了使用Makefile进行项目管理的方法。首先,文章指导读者安装SDCC(Small Device C Compiler),这是一个专为小型设备设计的C语言编译器,适合用于51单片机的开发。随后,通过具体的实例演示了如何配置Makefile文件,以实现代码的自动化编译与链接过程,从而提高开发效率。此外,还提供了常见问题的解决方案及优化建议,帮助开发者快速上手并解决实际开发中可能遇到的技术难题。 ... [详细]
  • 深入解析 C 语言与 C++ 之间的差异及关联
    深入解析 C 语言与 C++ 之间的差异及关联 ... [详细]
  • 在 Linux 系统中,`/proc` 目录实现了一种特殊的文件系统,称为 proc 文件系统。与传统的文件系统不同,proc 文件系统主要用于提供内核和进程信息的动态视图,通过文件和目录的形式呈现。这些信息包括系统状态、进程细节以及各种内核参数,为系统管理员和开发者提供了强大的诊断和调试工具。此外,proc 文件系统还支持实时读取和修改某些内核参数,增强了系统的灵活性和可配置性。 ... [详细]
  • 题目《UVa 11978 福岛核爆问题》涉及圆与多边形交集面积的计算及二分法的应用。该问题的核心在于通过精确的几何运算与高效的算法实现来解决复杂图形的面积计算。在实现过程中,特别需要注意的是对多边形顶点的平移处理,确保所有顶点包括最后一个顶点 \( p[n] \) 都经过正确的位移,以避免因细节疏忽导致的错误。此外,使用循环次数为50次的二分法能够有效提高算法的精度和稳定性。 ... [详细]
  • Go语言中的高效排序与搜索算法解析
    在探讨Go语言中高效的排序与搜索算法时,本文深入分析了Go语言提供的内置排序功能及其优化策略。通过实例代码,详细讲解了如何利用Go语言的标准库实现快速、高效的排序和搜索操作,为开发者提供了实用的编程指导。 ... [详细]
  • C++20 引入了指定初始化器(Designated Initializers),这一特性借鉴了 C# 的对象初始化器和 Kotlin 的 apply 范围函数。指定初始化器允许开发者在初始化结构体或类时,直接指定成员变量的值,提高了代码的可读性和简洁性。此外,该特性还支持嵌套初始化,使得复杂对象的初始化更加直观和灵活。本文将详细解析指定初始化器的语法、应用场景及其实现细节,并通过具体示例展示其在实际开发中的优势。 ... [详细]
  • 高效排序算法是提升数据处理速度的重要技术。通过优化排序算法,可以显著提高数据处理的效率和性能。本文介绍了几种常见的高效排序算法,如快速排序、归并排序和堆排序,并通过实例代码展示了它们的具体实现。实验结果表明,这些算法在大规模数据集上的表现尤为突出,能够有效减少数据处理时间,提升系统整体性能。 ... [详细]
  • 原题链接:http://codeforces.com/problemset/problem/848/A。题目要求我们构建一个特定的字符串。具体操作为:从给定字符串中选取若干部分,将其分割成两段,然后进行重新组合。本文将深入探讨该问题的解法,分析其背后的算法逻辑,并提供高效的实现方法。 ... [详细]
  • 在 Codeforces Global Round 3 的 B 题 "Inherent Talent" 中,主人公潇洒哥需要从 A 地前往 C 地,但两地之间没有直飞航班。他可以选择在 A 地和 B 地之间的中转航班,以便尽快抵达目的地 C。该问题的核心在于如何合理安排中转,以实现最短的旅行时间。 ... [详细]
author-avatar
隔岸观火2502884207
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有